/**    
 * 文件名：ColorHistogram.java    
 *    
 * 版本信息：    
 * 日期：2014年3月10日    
 * xyj 足下 xyj 2014     
 * 版权所有    
 *    
 */
package opencvtest.chapter04;

import static com.googlecode.javacv.cpp.opencv_core.IPL_DEPTH_8U;
import static com.googlecode.javacv.cpp.opencv_core.cvGetSize;
import static com.googlecode.javacv.cpp.opencv_core.cvSplit;
import static com.googlecode.javacv.cpp.opencv_imgproc.CV_BGR2HSV;
import static com.googlecode.javacv.cpp.opencv_imgproc.CV_HIST_SPARSE;
import static com.googlecode.javacv.cpp.opencv_imgproc.CV_THRESH_BINARY;
import static com.googlecode.javacv.cpp.opencv_imgproc.cvCalcHist;
import static com.googlecode.javacv.cpp.opencv_imgproc.cvCreateHist;
import static com.googlecode.javacv.cpp.opencv_imgproc.cvCvtColor;
import static com.googlecode.javacv.cpp.opencv_imgproc.cvThreshold;

import com.googlecode.javacv.cpp.opencv_core.CvMat;
import com.googlecode.javacv.cpp.opencv_core.CvSize;
import com.googlecode.javacv.cpp.opencv_core.IplImage;
import com.googlecode.javacv.cpp.opencv_imgproc.CvHistogram;

/**
 * Companion methods for `ColorHistogram`.
 */
public class ColorHistogram {

    int numberOfBins = 256;

    float minRange = 0.0f;

    float maxRange = 255.0f;

    /**
     * Reduce number of colors, described in OpenCV Cookbook Chapter 2.
     * 
     * @param image
     *            input image that will have colors modified after this call.
     * @param div
     *            color reduction factor.
     */
    public static void colorReduce(IplImage image, int div) {

        CvMat mat = image.asCvMat();

        // Total number of elements, combining components from each channel
        int nbElements = mat.rows() * mat.cols() * mat.channels();
        for (int i = 0; i < nbElements; i++) {
            // Convert to integer
            int v = (int) mat.get(i);
            // Use integer division to reduce number of values
            int newV = v / div * div + div / 2;
            // Put back into the image
            mat.put(i, newV);
        }

    }

    /**
     * Split channels in a 3 channel image, for instance, color image.
     * 
     * @param src
     *            3 channel image
     * @return array of 3 channels
     */
    public static IplImage[] splitChannels(IplImage src) {
        assert src != null : "Argument `src` cannot be null.";
        assert src.nChannels() == 3 : "Expecting 3 channel (color) image";

        CvSize size = cvGetSize(src);
        IplImage channel0 = IplImage.create(size, src.depth(), 1);
        IplImage channel1 = IplImage.create(size, src.depth(), 1);
        IplImage channel2 = IplImage.create(size, src.depth(), 1);

        cvSplit(src, channel0, channel1, channel2, null);

        return new IplImage[] { channel0, channel1, channel2 };
    }

    /**
     * Computes histogram of an image. Returned CvHistogram object has to be
     * manually deallocated after use using `cvReleaseHist`.
     * 
     * @param image
     *            input image
     * @return OpenCV histogram object
     */
    public CvHistogram getHistogram(IplImage image) {

        assert image != null;
        assert image.nChannels() == 3 : "Expecting 3 channel (color) image";

        // Allocate histogram object
        int dims = 3;
        int[] sizes = new int[] { numberOfBins, numberOfBins, numberOfBins };
        int histType = CV_HIST_SPARSE;
        float[] minMax = new float[] { minRange, maxRange };
        float[][] ranges = new float[][] { minMax, minMax, minMax };
        int uniform = 1;
        CvHistogram hist = cvCreateHist(dims, sizes, histType, ranges, uniform);

        // Split bands, as required by `cvCalcHist`
        IplImage channel0 = IplImage.create(cvGetSize(image), image.depth(), 1);
        IplImage channel1 = IplImage.create(cvGetSize(image), image.depth(), 1);
        IplImage channel2 = IplImage.create(cvGetSize(image), image.depth(), 1);
        cvSplit(image, channel0, channel1, channel2, null);

        // Compute histogram
        int accumulate = 0;
        IplImage mask = null;
        cvCalcHist(new IplImage[] { channel0, channel1, channel2 }, hist, accumulate, mask);
        return hist;

    }

    /**
     * Convert input image from RGB ro HSV color space and compute histogram of
     * the hue channel.
     * 
     * @param image
     *            RGB image
     * @param minSaturation
     *            minimum saturation of pixels that are used for histogram
     *            calculations. Pixels with saturation larger than minimum will
     *            be used in histogram computation
     * @return histogram of the hue channel, its range is from 0 to 180.
     */
    public CvHistogram getHueHistogram(IplImage image, int minSaturation) {

        assert image != null;
        assert image.nChannels() == 3 : "Expecting 3 channel (color) image";

        // Convert RGB to HSV color space
        IplImage hsvImage = IplImage.create(cvGetSize(image), image.depth(), 3);
        cvCvtColor(image, hsvImage, CV_BGR2HSV);

        // Split the 3 channels into 3 images
        IplImage[] hsvChannels = splitChannels(hsvImage);

        IplImage saturationMask = null;
        if (minSaturation > 0) {
            saturationMask = IplImage.create(cvGetSize(hsvImage), IPL_DEPTH_8U, 1);
            cvThreshold(hsvChannels[1], saturationMask, minSaturation, 255, CV_THRESH_BINARY);

            // Compute histogram of the hue channel
            Histogram1D h1D = new Histogram1D();
            h1D.setRanges(0, 180);
            return h1D.getHistogram(hsvChannels[0], saturationMask);

        }

        return null;

    }
}
